// LPMolecViewer.cpp : Implementation of CLPMolecViewer
//GDIplus (Use Microsoft Platform SDK)


#include "stdafx.h"

#include "LPMolecViewerControl.h"

#include "LPMolecViewer.h"


//#include <Gdiplus.h> 
//using namespace Gdiplus; 
//#pragma comment(lib, "gdiplus.lib")

//#include "gdiplus.h" //Doesn't work because this uses old version of SDK
//TODO: For saving to PNG try using gdiplus wrapper lib created with VC++2008
//located at 
// C:\Documents and Settings\Luis Perdigao\My Documents\Visual Studio 2008\Projects\LPSaveBitmapGDIPlusLib

//Wrapper-helper class for saving HBITMAP to png file with alpha channel
//# include "LPSaveBitmapGDIPlusLib.h"

//Math routines with geometric algebra
#include "LPMath.c"

//Try using GDIPlus helper lib created using VC++6
//#include "LPSaveBitmapGDIPlusLib.h"
//#include "LPSaveBitmapGDIPlusDLL.h"
#include "LPSaveBitmapGDIPlusDll6\LPSaveBitampGDIPlusDll6.h"

/////////////////////////////////////////////////////////////////////////////
// CLPMolecViewer

//Constructor
CLPMolecViewer::CLPMolecViewer(){
	m_bWindowOnly = true; //FOR SOME REASON IT NEEDS THIS otherwise it will draw nothing
	m_bMouseCaptured=false;
	
	//initialises variables
	m_theta=0;
	m_phi=0;
	m_psi=0;
		
	//Geom Algeb rotation variables
	m_GARotR0=1;
	m_GARotR12=0; m_GARotR13=0; m_GARotR23=0; //default no rotation

	M_2PI=6.283185307179586476925286766559;
	m_height=1;
	m_width=1;
	m_bAnimate=false;
	m_nTimer=0;
	m_TimerInterval=100;
	m_sphereResolution=15;
	m_connectorResolution=10;
	m_isglinit=false;
	m_zoomfactor=1.0;
	m_perspective=60;

	m_hdc=NULL;	//Initialises handle to window area
	m_hRC=NULL; //Initialises handle to rectangle client area
		
	m_Molecule=NULL; //Starts with no molecules
	m_isModelDirty=false;

	m_autoconnect=true;
		
	m_bFileOpen=false;
	m_FileLocation=_T("molecule.xyz");

	//this->ReadMolFile(m_FileLocation); //Moved to OnCreate
}

BOOL CLPMolecViewer::SetupPixelFormat(HDC hdc)
{
    //For more information, see
	//http://msdn.microsoft.com/en-us/library/ms537559(VS.85).aspx

	static PIXELFORMATDESCRIPTOR pfd =
    {
        sizeof(PIXELFORMATDESCRIPTOR),   // size of this pfd
            1,                           // version number
            PFD_DRAW_TO_WINDOW |         // support window
            PFD_SUPPORT_OPENGL |         // support OpenGL
            PFD_DOUBLEBUFFER,            // double buffered
            PFD_TYPE_RGBA,               // RGBA type
            24,                          // 24-bit color depth
            0, 0, 0, 0,	0, 0,				 //red bits,shift; green,shift; blue,shift; (ignored)
			0, 0,						 //alpha bits, shift (ignored)
            0,                           // no accumulation buffer
            0, 0, 0, 0,                  // accum bits ignored
            32,                          // 32-bit z-buffer
            0,                           // no stencil buffer
            0,                           // no auxiliary buffer
            PFD_MAIN_PLANE,              // main layer
            0,                           // reserved
            0, 0, 0                      // layer masks ignored
    };

	//Setting up a PFD with alpha bits does not help in getting
	// a bitmap with alpha=0 when using glReadPixels

    int pixelformat;


	// The ChoosePixelFormat function attempts to match an appropriate
	// pixel format supported by a device context to a given pixel format specification.
	// http://msdn.microsoft.com/en-us/library/ms537556(VS.85).aspx
	
	// Basically this will find the best match for your display device
    if ((pixelformat = ChoosePixelFormat(hdc, &pfd)) == 0)
    {
        ATLASSERT(FALSE);
        return FALSE;
    }


	//The SetPixelFormat function sets the pixel format of the specified device context
	// to the format specified by the iPixelFormat index.
	// http://msdn.microsoft.com/en-us/library/ms537559(VS.85).aspx
	
	// Sets the best pixel format to be the current one
	// This needs to be done before calling wglCreateContext()
    if (SetPixelFormat(hdc, pixelformat, &pfd) == FALSE)
    {
        ATLASSERT(FALSE);
        return FALSE;
    }

    return TRUE;

}

void CLPMolecViewer::CreateContext(HDC hdc)
{
	// It needs to set up pixel format before "creating opengl context"
	// Pixel format will be associated with the handle hdc

    if (!SetupPixelFormat(hdc))
        return;
	
	// Gets pixel format
	// REMARK: May not need this because this is done in SetupPixelFormat anyway
    //PIXELFORMATDESCRIPTOR pfd;
	//int n = GetPixelFormat(hdc);
	//DescribePixelFormat(hdc, n, sizeof(pfd), &pfd);

    m_hRC = wglCreateContext(hdc);
    wglMakeCurrent(hdc, m_hRC);		

}

LRESULT CLPMolecViewer::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	//this->ReadMolFile(m_FileLocation);

    EnableOpenGL();

	//Sets timer
	if (m_bAnimate!=false){
		m_nTimer=SetTimer(1, m_TimerInterval);
	}
	//else KillTimer(m_nTimer);
	
    return 0;
}

LRESULT CLPMolecViewer::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
  
	DisableOpenGL();
	//LP:Moved to new function DisableOpenGL()
	/*
	wglMakeCurrent(NULL, NULL);

  if (m_hRC)
  {
    wglDeleteContext(m_hRC);
    m_hRC = NULL;
  }*/

	return 0;
}

LRESULT CLPMolecViewer::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    // when resized, recreate the device context

	//Modified to use Enable/disable OpneGL functions
	DisableOpenGL();
	EnableOpenGL();
	/*
    wglMakeCurrent(NULL,    NULL);
    if (m_hRC)
    {
        wglDeleteContext(m_hRC);
        m_hRC = NULL;
    }
    HDC hdc = GetDC();
    RECT rc;
    GetClientRect(&rc);
    CreateContext(hdc, rc);
	*/
	drawGLScene();
	//FireViewChange();
	//m_width=wParam;
	//m_height=lParam;

    return 0;

}

/*
HRESULT OnDraw(ATL_DRAWINFO& di)
{
	RECT& rc = *(RECT*)di.prcBounds;
	Rectangle(di.hdcDraw, rc.left, rc.top, rc.right, rc.bottom);

	SetTextAlign(di.hdcDraw, TA_CENTER|TA_BASELINE);
	LPCTSTR pszText = _T("ATL 3.0 : LPMolecViewer");
	TextOut(di.hdcDraw, 
		(rc.left + rc.right) / 2, 
		(rc.top + rc.bottom) / 2, 
		pszText, 
		lstrlen(pszText));

	return S_OK;
}
*/


HRESULT CLPMolecViewer::OnDraw(ATL_DRAWINFO& di){

	HDC hdc;
	
	//drawGLScene();

	if (m_hdc==NULL){
		hdc=di.hdcDraw;
		//Gets rectangle form drawing area of the control
		RECT& rc = *(RECT*)di.prcBounds;
				
		basic_string<TCHAR> s1=_T("LPMolecViewer version 4.5");
		basic_string<TCHAR> s2=_T("");
		//basic_string<TCHAR> s3=_T(VS_VERSION_INFO);

		if (m_bFileOpen){	
			//s2=s2.append("File: ");
			s2=s2.append(_T("File: "));
			s2=s2.append(m_FileLocation);	
		}
		else{
			//s2=s2.append("No File: ");
			s2=s2.append(_T("No File: "));
			s2=s2.append(m_FileLocation);
		}
	
		//Fills rectangle with brush color (background color)
		//Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
		//Writes text s1 and s2 on control
		//LPCTSTR pszText = _T(s1.c_str());
		LPCTSTR pszText = _T(s1.c_str());

		SetTextAlign(hdc, TA_CENTER|TA_BASELINE);
		TextOut(hdc, (rc.left+rc.right)/2, rc.bottom-10, pszText, lstrlen(pszText));
		pszText = _T(s2.c_str());
		TextOut(hdc, (rc.left+rc.right)/2,(rc.top+rc.bottom)/2, pszText, lstrlen(pszText));
	
	}else{
		drawGLScene();
	}


	//Code below doesn't work, don't know why
	//Maybe because before using DrawText, the pallette needs to be initialised
	//CComBSTR testbstr=_T("Test");
	//SetTextColor(hdc, RGB(128,128,255));
	//SetBkMode(hdc, TRANSPARENT);
	//USES_CONVERSION;
	//LPCTSTR lpsz = OLE2T(testbstr);
	//DrawText(hdc, lpsz, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

	//drawGLScene(); //Draw scene (if opengl has been initialised)

	return S_OK;
}

void CLPMolecViewer::InitGLScene()
{
	
	m_isglinit=false;
	
	//glClearDepth(10.0f);	
	glEnable(GL_DEPTH_TEST);

	projectionGLScene(m_perspective,1, 1000);
	//glMatrixMode(GL_PROJECTION);  // Sets the camera(projection) matrix
	//glLoadIdentity();
	// Set the viewport to be the entire window
	//if(m_height == 0) m_height = 1;
	//float ratio = 1.0* m_width / m_height;
	//glViewport(0, 0, m_width, m_height); //upper left corner 0,0, bottom right w,h
	// Set the correct perspective.
	// 60 degrees view angle,
	// ratio= relation between the width and height of the viewport
	// 1 and 1000 define near and far clipping planes
	//gluPerspective(60,ratio,1,1000); 

	glMatrixMode(GL_MODELVIEW); // Sets the model matrix where objects are drawn

	//Defines how quadrics are drawn
	if (m_qobj!=NULL) gluDeleteQuadric(m_qobj);
	m_qobj=gluNewQuadric();
	gluQuadricDrawStyle(m_qobj,GLU_FILL);
	gluQuadricNormals(m_qobj,GLU_SMOOTH);

	//glClearColor(1.0f,1.0f,1.0f,1.0f); //Sets the clear color - background color to white
	glClearColor(1.0f,1.0f,1.0f,0.0f); //Try with background alpha transparent

	lightGLScene();		//Lights

	//Materials properties
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_DIFFUSE);

	//Creates list with the 3D model
	//(better than drawing individually, and faster)
	InitDisplayLists();

	//SwapBuffers(m_hdc);
	m_isglinit=true;		//Flags that GL has initiated
	m_isModelDirty=false;	//Flags that model is now clean

}

void CLPMolecViewer::projectionGLScene(double angle, double nearplane, double farplane){
	//Sets the projection matrix
	//Viewport to whole window,
	//Perspective to value set

	glMatrixMode(GL_PROJECTION);  // Sets the camera(projection) matrix

	glLoadIdentity();
	
	// Set the viewport to be the entire window
	if(m_height == 0) m_height = 1;
	float ratio = 1.0* m_width / m_height;
	glViewport(0, 0, m_width, m_height); //upper left corner 0,0, bottom right w,h

	// Set the correct perspective.
	// 60 degrees view angle,
	// ratio= relation between the width and height of the viewport
	// 1 and 1000 define near and far clipping planes
	//gluPerspective(60,ratio,1,1000); 
	gluPerspective(angle,ratio,nearplane,farplane);

}

void CLPMolecViewer::ReadMolFile(TCHAR *my_file)
{
	m_bFileOpen=false;
	
	//LP: Tried, gives error
	if (m_Molecule!=NULL) delete m_Molecule;

	m_Molecule=new LPMolecule();
	if (m_Molecule->LoadMolecule(my_file,m_autoconnect)){
		m_Molecule->centerMolecule();				//Center molecule
		m_CameraDist=m_Molecule->getDimension()*2;	//Set camera distance
		m_bFileOpen=true;		//Signals that a file is open
		m_isModelDirty=true;	//Flags that model needs to be rebuilt.
		
	}else{
		m_bFileOpen=false;
	}
}

void CLPMolecViewer::drawGLScene()
{
	if (m_isglinit){

		//Check if there were changes to the molecular model
		if (m_isModelDirty==true){
			InitDisplayLists();		//Rebuild 3D model
			m_isModelDirty=false;
		}

		wglMakeCurrent(m_hdc, m_hRC);
		
		//Sets projection
		projectionGLScene(m_perspective,1, 1000);

		//Ensure we are working with model matrix
		glMatrixMode(GL_MODELVIEW);

		//Set camera position
		cameraGLScene();
		
		//Rotate the scene before drawing
		//rotateGLScene();
		rotateGLSceneGA();

		//Clear scene
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	


		//Draw scene by calling the 'List' containing the model
		glCallList(m_molDispList);

		glFinish();

		SwapBuffers(m_hdc); //Renders image (double buffering)
	}
}


void CLPMolecViewer::rotateGLScene()
{
	//OLD NOT USED ANYMORE
	
	//Check for large angles and reset
	if (m_theta>360) m_theta=m_theta-360;
	if (m_phi>360) m_phi=m_phi-360;
	if (m_psi>360) m_psi=m_psi-360;

	if (m_theta<0) m_theta=m_theta+360;
	if (m_phi<0) m_phi=m_phi+360;
	if (m_psi<0) m_psi=m_psi+360;


	//Rotate the object around Y axis - idle rotation movement
	glRotatef(m_theta,0,1.0,0);

	//Rotate object around its Zaxis - keybord movement ??
	glRotatef(m_phi,0,0,1.0);

	//Rotate object around its Xaxis
	glRotatef(m_psi,1.0,0,0);


}

void CLPMolecViewer::rotateGLSceneGA()
{
	//Uses the geom algebra rotation variables to rotate scene
	//m_GARotR

	//Gets the vector axis of rotation and angle

	double N1, N2,N3, anglerad, angledeg;

	LPGAConvertRotorToRotationVector(m_GARotR0,
		m_GARotR12,
		m_GARotR13,
		m_GARotR23,
		&N1,&N2,&N3,&anglerad);
	
	angledeg=anglerad*360/M_2PI;

	//Rotate the object calculated axis
	glRotatef(angledeg,N1,N2,N3);


	//Refreshes angles values here
	m_psi=angledeg;
	//Calculates psi and phi from normal vector N
	double length=sqrt(N1*N1+N2*N2+N3*N3); //length should be 1
	m_theta=0;
	if (length!=0) m_theta=acos(N3/length)*360/M_2PI;
	m_phi=atan2(N2,N1)*360/M_2PI; //Try this
	
}

void CLPMolecViewer::cameraGLScene()
{
	glLoadIdentity(); //Loads the identity matrix, initalises modelview matrix
					//This also avoids previous transformations to affect camera position
	
	//Sets the camera position
	
	gluLookAt(0.0,0.0,m_CameraDist*m_zoomfactor, //Camera position
		      0.0,0.0,-20.0, //Point we are looking at
			  0.0f,1.0f,0.0f); //Tilting of the camera


}

void CLPMolecViewer::drawGLMolecule()
{
	int i;
	LPConnector* Connector1;
	LPAtom* Atom1;

	//Draw bonds first
	int ncon=m_Molecule->getNumberConnectors();
	for (i=0;i<ncon;i++){
		Connector1=m_Molecule->ListConnectors[i];
		drawGLConnector(Connector1,m_connectorResolution);
	}

	//Draw atoms
	//int sphereResolution=20;
	int nat=m_Molecule->getNumberAtoms();
	//if (nat>100) sphereResolution=15;
	//if (nat>200) sphereResolution=10;
	//if (nat>400) sphereResolution=7;
	for (i=0;i<nat;i++){
		Atom1=m_Molecule->ListAtoms[i];
		drawGLAtom(Atom1, m_sphereResolution);
	}

}


void CLPMolecViewer::drawGLAtom(LPAtom *Atom, int resol)
{
	//TODO setup the colour and size before drawing atom
	double radius;
	GLubyte colourR, colourG, colourB;
	colourR=(GLubyte)Atom->colour[0];
	colourG=(GLubyte)Atom->colour[1];
	colourB=(GLubyte)Atom->colour[2];

	glPushMatrix();
		//glColor3f(1.0,0.0,0.0); //set current color
		//glColor3ub(colourR,colourG,colourB); //Sets the colour according to the element
		glColor3ub(colourR,colourG,colourB); //Sets the colour according to the element
		glTranslated(Atom->x,Atom->y,Atom->z); //Moves to specified position
		radius=Atom->radius/2;

		//Draws glu sphere
		gluSphere(m_qobj,
			radius,		//radius
			resol,		//slices
			resol);		//stacks
		
	glPopMatrix(); //Comes back to original position
	

}


void CLPMolecViewer::drawGLConnector(LPConnector *Connector, int resol)
{
	// A cylinder is drawn using glu function glucylinder
	
	//Determines the length of the cylinder
	double length;
	double x1,y1,z1;
	double x2,y2,z2;
	double x,y,z;
	//double xav,yav,zav;

	double alfa, beta;
	double rx,ry,rz; //axis of rotation

	x1=Connector->Atom1->x;
	y1=Connector->Atom1->y;
	z1=Connector->Atom1->z;
	x2=Connector->Atom2->x;
	y2=Connector->Atom2->y;
	z2=Connector->Atom2->z;
	
	x=x1-x2;
	y=y1-y2;
	z=z1-z2;

	//xav=(x1+x2)/2;
	//yav=(y1+y2)/2;
	//zav=(z1+z2)/2;

	length=sqrt(x*x+y*y+z*z);
	alfa=0;

	beta=0;
	if (length!=0) beta=acos(z/length)*360/M_2PI;

	//Determine axis of rotation ( z x r external product)
	rx=-y; ry=+x; rz=0;

	glPushMatrix();
	
	
	glColor3ub((char)127,(char)127,(char)127); //Sets the colour to grey
	

	glTranslated(x2,y2,z2); //Moves to specified position

	glRotated(beta,rx,ry,rz);


	//Draws cylinder at origin using length as height
	gluCylinder(m_qobj,
		0.12,	//base radius of 0.2 Angstrons
		0.12,	//top radius
		length, //height
		resol,	//slices
		1);		//stacks

	glPopMatrix(); //Comes back to original position
	

}


void CLPMolecViewer::InitDisplayLists()
{
	//Defines a display list for drawing the whole molecule
	m_molDispList=glGenLists(1);
	glNewList(m_molDispList,GL_COMPILE);
		drawGLMolecule();
	glEndList();

}

STDMETHODIMP CLPMolecViewer::get_FileLocation(BSTR *pVal)
{
	// TODO: Add your implementation code here
	CComBSTR bstStr(this->m_FileLocation);

	*pVal=bstStr.Detach();

	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_FileLocation(BSTR newVal)
{
	//Using unicode
	USES_CONVERSION;
	//m_FileLocation=_T(OLE2T(newVal));
	m_FileLocation=new TCHAR[MAX_PATH];
	::wcstombs(m_FileLocation,newVal,MAX_PATH);

	//m_FileLocation=new TCHAR(OLE2T(newVal));

	//m_FileLocation=new char((char*)newVal);
	//TCHAR *a=new char[500];
	
	//::wcstombs(a,newVal,499);
	//m_FileLocation=a;

	m_bFileOpen=false;
	this->ReadMolFile(m_FileLocation);

	//drawGLScene();
	//FireViewChange();
	FireViewChange();
	SetDirty(true);
	
	return S_OK;
}

void CLPMolecViewer::animationStep()
{
	
	//m_theta=m_theta+1;

	//m_phi=m_phi+0.65;

	//m_psi=m_psi+.3;

	//Added, use Geom Algebra variables rotation
	//Rotate around yy axis
	rotateGAYaxis(0.02);
}


STDMETHODIMP CLPMolecViewer::get_Animate(BOOL *pVal)
{
	*pVal=m_bAnimate;
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_Animate(BOOL newVal)
{
	m_bAnimate=newVal;
	if (m_bAnimate!=false){
		m_nTimer=SetTimer(1,m_TimerInterval);
	}else{
		KillTimer(m_nTimer);
	}
	SetDirty(true);
	return S_OK;
}


STDMETHODIMP CLPMolecViewer::get_SphereResolution(short *pVal)
{
	*pVal=m_sphereResolution;
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_SphereResolution(short newVal)
{
	if (newVal>0 && newVal<SPHERE_RESOLUTION_LIMIT){
		m_sphereResolution=newVal;
		//InitDisplayLists();
		m_isModelDirty=true; //flags to rebuild model list
		SetDirty(true);
	}
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::get_ConnectorResolution(short *pVal)
{
	*pVal=m_connectorResolution;
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_ConnectorResolution(short newVal)
{
	if (newVal>0 && newVal<CONNECTOR_RESOLUTION_LIMIT){
		m_connectorResolution=newVal;
		//InitDisplayLists();
		m_isModelDirty=true;
		SetDirty(true);
	}
	return S_OK;
}


STDMETHODIMP CLPMolecViewer::get_TimerInterval(short *pVal)
{
	*pVal=m_TimerInterval;

	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_TimerInterval(short newVal)
{
	if (newVal>0){
		m_TimerInterval=newVal;
		SetDirty(true);
	}
	return S_OK;
}


STDMETHODIMP CLPMolecViewer::Load (IStream *pStream)
{
	//Persisted stream functions.
	//Allows to load control properties from files where they are used
	//such as PowerPoint or visualbasic.

	DWORD nActual;
	HRESULT hRes = E_UNEXPECTED;

	// Check if the passed interface pointer is valid or not.

	if (NULL == pStream) {
		return (E_POINTER);
	}

	// Load each parameter value from the stream. Make sure that the order of loading is same as
	// the order inwhich parameters were saved to stream.

	// NOTE: Make sure that you set the m_str member variable to NULL. This has to be dome to avoid
	//		 assertion inside the ATL code.
	//
	//		 Code from ATLBASE.H file...................
	//		 **********************************************************
	//		 HRESULT ReadFromStream(IStream* pStream)
	//		 {
	//			ATLASSERT(pStream != NULL);
	//			ATLASSERT(m_str == NULL); // should be empty
	//			..
	//		 *********************************************************	
	//
	pStream->Read (&m_theta, sizeof (m_theta), &nActual);
	pStream->Read (&m_psi, sizeof (m_psi), &nActual);
	pStream->Read (&m_phi, sizeof (m_phi), &nActual);
	//New angles requires GA variables to reset
	computeGARotorFromAngles();

	pStream->Read (&m_zoomfactor, sizeof (m_zoomfactor), &nActual);

	pStream->Read (&m_autoconnect, sizeof (m_autoconnect), &nActual);

	pStream->Read (&m_bAnimate, sizeof (m_bAnimate), &nActual);
	//put_Animate(m_bAnimate);

	pStream->Read (&m_connectorResolution, sizeof (m_connectorResolution), &nActual);
	//put_ConnectorResolution(m_connectorResolution); //maybe not necessary

	pStream->Read (&m_sphereResolution, sizeof (m_sphereResolution), &nActual);
	//put_SphereResolution(m_sphereResolution);

	pStream->Read (&m_TimerInterval, sizeof (m_TimerInterval), &nActual);


	CComBSTR m_FileLocation1;
	m_FileLocation1.m_str = NULL;
	m_FileLocation1.ReadFromStream (pStream);
	put_FileLocation(m_FileLocation1.m_str);

	//perspectiveGL
	pStream->Read (&m_perspective, sizeof (m_perspective), &nActual);


	return (S_OK);
}

//
//	Function:
//		Save(IStream *pStream, BOOL fClearDirty)
//
//	Parameters:
//		pStream 		IStream pointer to the stream into which the object should be saved.  
//		fClearDirty		Indicates whether to clear the dirty flag after the save is complete. If
//						TRUE, the flag should be cleared. If FALSE, the flag should be left
//						unchanged.
//
//	Return Values:
//		S_OK			The object was successfully saved to the stream.
//		STG_E_CANTSAVE 	The object could not save itself to the stream. This error could indicate,
//						for example, that the object contains another object that is not
//						serializable to a stream or that an ISequentialStream::Write call returned
//						STG_E_CANTSAVE. 
//		STG_E_MEDIUMFULL The object could not be saved because there is no space left on the
//						 storage device. 
//		E_FAIL			Error
//
//	Comments:
//		Saves an object into the specified stream and indicates whether the object should reset
//		its dirty flag.
//

STDMETHODIMP CLPMolecViewer::Save(IStream *pStream, BOOL fClearDirty)
{
	//Persistent stream function
	//For saving current state into files/programs where the control is used
	//such as powerpoint or visualbasic
	DWORD nActual;
	HRESULT hRes = E_FAIL;

	// Make sure that IStream pointer is not null.

	if (pStream == NULL) {
		ATLTRACE (_T ("Null stream pointer!\n"));
		return (E_POINTER);
	}

	// Write the control parameters to the stream.
	// NOTE: Following implementation doesn't check for the difference between size to be saved
	//		 and the size actually saved to report error. Its being done for only first one. The
	//		 error for the other properties can be checked in same way.
	pStream->Write (&m_theta, sizeof (m_theta), &nActual);
	pStream->Write (&m_psi, sizeof (m_psi), &nActual);
	pStream->Write (&m_phi, sizeof (m_phi), &nActual);
	pStream->Write (&m_zoomfactor, sizeof (m_zoomfactor), &nActual);
	pStream->Write (&m_autoconnect, sizeof (m_autoconnect), &nActual);

	pStream->Write (&m_bAnimate, sizeof (m_bAnimate), &nActual);
	pStream->Write(&m_connectorResolution, sizeof(m_connectorResolution),&nActual);
	pStream->Write(&m_sphereResolution, sizeof(m_sphereResolution),&nActual);
	pStream->Write(&m_TimerInterval, sizeof(m_TimerInterval),&nActual);
	

	CComBSTR bstStr(this->m_FileLocation);
	bstStr.WriteToStream (pStream);

	pStream->Write(&m_perspective, sizeof(m_perspective),&nActual);

	/*BOOL b;
	get_Animate(&b);	
	nToWrite = sizeof (BOOL);
	hRes = pStream->Write (&b, nToWrite, &nActual);
	if (SUCCEEDED (hRes) && nToWrite != nActual) {
		return (STG_E_CANTSAVE);
	}*/

	
	/*nToWrite = sizeof (m_nRadius);
	hRes = pStream->Write (&m_nRadius, nToWrite, &nActual);
	if (SUCCEEDED (hRes) && nToWrite != nActual) {
		return (STG_E_CANTSAVE);
	}

	pStream->Write (&m_clrFillColor, sizeof (OLE_COLOR), &nActual);
	pStream->Write (&m_clrBackColor,sizeof (OLE_COLOR), &nActual);
	pStream->Write (&m_nBackStyle, sizeof (LONG), &nActual);
	pStream->Write (&m_clrBorderColor, sizeof (OLE_COLOR), &nActual);
	pStream->Write (&m_nBorderStyle, sizeof (LONG), &nActual);
	pStream->Write (&m_bBorderVisible, sizeof (BOOL), &nActual);
	pStream->Write (&m_nBorderWidth, sizeof (LONG), &nActual);
	pStream->Write (&m_nDrawMode, sizeof (LONG), &nActual);
	pStream->Write (&m_nDrawStyle, sizeof (LONG), &nActual);
	pStream->Write (&m_nDrawWidth, sizeof (LONG), &nActual);
	pStream->Write (&m_bEnabled, sizeof (BOOL), &nActual);
	pStream->Write (&m_nFillStyle, sizeof (LONG), &nActual);
	pStream->Write (&m_clrForeColor, sizeof (OLE_COLOR), &nActual);
	pStream->Write (&m_bValid, sizeof (BOOL), &nActual);
	pStream->Write (&m_nFillPattern, sizeof (m_nFillPattern), &nActual);
	m_bstrText.WriteToStream (pStream);

	// Since IPersistStream interface is implemented by IFont, make use of it to persist the
	// font property.

	IPersistStream *pFontStream;

	hRes = m_pFont->QueryInterface (IID_IPersistStream, (void **) &pFontStream);
	if (FAILED (hRes)) {
		Error (_T ("Failed to create stream interface for font!"));
		return (E_FAIL);
	}

	hRes = pFontStream->Save (pStream, true);
	if (FAILED (hRes)) {
		Error (_T ("Failed to create stream interface for font!"));
		return (E_FAIL);
	}
	pFontStream->Release ();
	*/

	SetDirty (!fClearDirty);

	return (S_OK);
}

//
//	Function:
//		GetSizeMax (ULARGE_INTEGER *pcbSize)
//
//	Parameters:
//		pcbSize   		Pointer to size of stream needed to save object .
//
//	Return Values:
//		S_OK			The size was successfully returned.  
//		E_FAIL			The size was not successfully returned. 
//
//	Comments:
//		This method returns the size needed to save an object. You can call this method to
//		determine the size and set the necessary buffers before calling the
//		IPersistStream::Save method
//
//	NOTE: The followinf MACRO from OBJBASE.H is used to set the size in ULARGE_INTEGER
//			#define ULISet32(li, v) ((li).HighPart = 0, (li).LowPart = (v))
//

STDMETHODIMP CLPMolecViewer::GetSizeMax (ULARGE_INTEGER *pcbSize)
{
	HRESULT hRes = E_POINTER;
	ULONG ulsize;

	// Make sure that pointer is not null.

	if (pcbSize == NULL) {
		return (hRes);
	}

	// Add the size of each property.
/*
	ulsize = sizeof (m_nRadius);
	ulsize += sizeof (m_clrFillColor);
	ulsize += sizeof (m_nBackStyle);
	ulsize += sizeof (m_clrBorderColor);
	ulsize += sizeof (m_nBorderStyle);
	ulsize += sizeof (m_bBorderVisible);
	ulsize += sizeof (m_nBorderWidth);
	ulsize += sizeof (m_nDrawMode);
	ulsize += sizeof (m_nDrawStyle);
	ulsize += sizeof (m_nDrawWidth);
	ulsize += sizeof (m_bEnabled);
	ulsize += sizeof (m_nFillStyle);
	ulsize += sizeof (m_clrForeColor);
	ulsize += sizeof (m_bValid);
	ulsize += sizeof (m_nFillPattern);
	ulsize += sizeof (OLECHAR) * m_bstrText.Length ();
*/

	CComBSTR bstStr(this->m_FileLocation);
	ulsize = sizeof (OLECHAR) *bstStr.Length ();

	ulsize += sizeof (m_bAnimate);  //m_bAnimate
	

	// Since IPersistStream interface is implemented by IFont, make use of it to get the size
	// to persist this property.
/*
	IPersistStream *pFontStream;

	hRes = m_pFont->QueryInterface (IID_IPersistStream, (void **) &pFontStream);
	if (FAILED (hRes)) {
		Error (_T ("Failed to create stream interface for font!"));
		return (E_FAIL);
	}

	hRes = pFontStream->GetSizeMax (pcbSize);
	if (FAILED (hRes)) {
		Error (_T ("Failed to create stream interface for font!"));
		return (E_FAIL);
	}
*/

	ulsize += pcbSize->LowPart;
	// Set the size of control.

	ULISet32 (*pcbSize, ulsize);

	return (S_OK);
}


void CLPMolecViewer::EnableOpenGL()
{
	//Only use this function if windowed m_hdc, through OnCreate()

	SetFocus();
    
	RECT rc;
    GetClientRect(&rc); //Gets rectangular coordinates of the client area
    m_width = rc.right - rc.left;
    m_height = rc.bottom - rc.top;

	m_hdc = GetDC();		//Gets Device Context for the entire screen    
	CreateContext(m_hdc);	//Creates OpenGL context on the rectangle specified
							//This function also sets m_hRC (opengl rectangle handle)

	if (m_bFileOpen) InitGLScene();
}

void CLPMolecViewer::DisableOpenGL()
{
	wglMakeCurrent(NULL, NULL);

	if (m_hRC)
	{
		wglDeleteContext(m_hRC);
		m_hRC = NULL;
		
	}
	m_hdc=NULL;
	m_isglinit=false;

}

STDMETHODIMP CLPMolecViewer::get_AutoConnect(BOOL *pVal)
{
	*pVal=m_autoconnect;
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_AutoConnect(BOOL newVal)
{
	m_autoconnect=newVal;
	
	//Reopens file
	this->ReadMolFile(m_FileLocation);
	//if (m_bFileOpen){
		// when new file opened, recreate the device context
		//DisableOpenGL();
		//EnableOpenGL();
	//}
	FireViewChange();
	SetDirty(true);
	
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::get_ZoomFactor(double *pVal)
{
	*pVal=m_zoomfactor;
	return S_OK;

}

STDMETHODIMP CLPMolecViewer::put_ZoomFactor(double newVal)
{
	m_zoomfactor=newVal;;

	//drawGLScene();
	FireViewChange();
	SetDirty(true);
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::get_AngleTheta(double *pVal)
{
	*pVal=m_theta;

	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_AngleTheta(double newVal)
{
	m_theta=newVal;
	computeGARotorFromAngles();
	//initDisplayLists();
	FireViewChange();
	SetDirty(true);

	return S_OK;
}

STDMETHODIMP CLPMolecViewer::get_AnglePsi(double *pVal)
{
	*pVal=m_psi;
	// TODO: Add your implementation code here

	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_AnglePsi(double newVal)
{
	m_psi=newVal;
	computeGARotorFromAngles();
	//initDisplayLists();
	FireViewChange();
	SetDirty(true);

	return S_OK;
}

STDMETHODIMP CLPMolecViewer::get_AnglePhi(double *pVal)
{
	*pVal=m_phi;
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_AnglePhi(double newVal)
{
	m_phi=newVal;
	computeGARotorFromAngles();
	//initDisplayLists();
	FireViewChange();
	SetDirty(true);

	return S_OK;
}

void CLPMolecViewer::lightGLScene()
{
	glMatrixMode(GL_MODELVIEW); // Sets the model matrix where objects are drawn
	
	//Sets diffuse light
	GLfloat m_light_position[]={0, 500, 1000, 0.0}; //Light at (0,500,1000)
	GLfloat m_light_diffuse[]={1.0, 1.0, 1.0, 1.0};
	// Enable a single OpenGL ligh.  GL_LIGHT0 
	glLightfv(GL_LIGHT0, GL_DIFFUSE, m_light_diffuse);
	glLightfv(GL_LIGHT0, GL_POSITION, m_light_position);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHTING);

}

void CLPMolecViewer::rotateGAYaxis(double AngleRad)
{
	//Added, use Geom Algebra variables rotation
	//Rotate around yy axis

	double R0, R12, R13, R23;
	double *A=new double[4];

	double tcos=cos(AngleRad/2);
	double tsin=sin(AngleRad/2);
	
	// Rotor for rotation around the yy axis
	R0=tcos;
	R12=0;
	R13=-tsin;
	R23=0;

	//Applies rotation
	LPGARotorMultiplication(R0,R12,R13,R23,
		m_GARotR0,m_GARotR12,m_GARotR13,m_GARotR23,
		&A[0],&A[1],&A[2],&A[3]);

	m_GARotR0=A[0];
	m_GARotR12=A[1];
	m_GARotR13=A[2];
	m_GARotR23=A[3];

}

void CLPMolecViewer::rotateGAXaxis(double AngleRad)
{
	//Added, use Geom Algebra variables rotation
	//Rotate around xx axis

	double R0, R12, R13, R23;
	double *A=new double[4];

	double tcos=cos(AngleRad/2);
	double tsin=sin(AngleRad/2);
	
	// Rotor for rotation around the xx axis
	R0=tcos;
	R12=0;
	R13=0;
	R23=-tsin;

	//Applies rotation
	LPGARotorMultiplication(R0,R12,R13,R23,
		m_GARotR0,m_GARotR12,m_GARotR13,m_GARotR23,
		&A[0],&A[1],&A[2],&A[3]);

	m_GARotR0=A[0];
	m_GARotR12=A[1];
	m_GARotR13=A[2];
	m_GARotR23=A[3];

}

void CLPMolecViewer::rotateGAZaxis(double AngleRad)
{
	//Added, use Geom Algebra variables rotation
	//Rotate around zz axis

	double R0, R12, R13, R23;
	double *A=new double[4];

	double tcos=cos(AngleRad/2);
	double tsin=sin(AngleRad/2);
	
	// Rotor for rotation around the zz axis
	R0=tcos;
	R12=-tsin;
	R13=0;
	R23=0;

	//Applies rotation
	LPGARotorMultiplication(R0,R12,R13,R23,
		m_GARotR0,m_GARotR12,m_GARotR13,m_GARotR23,
		&A[0],&A[1],&A[2],&A[3]);

	m_GARotR0=A[0];
	m_GARotR12=A[1];
	m_GARotR13=A[2];
	m_GARotR23=A[3];

}

void CLPMolecViewer::computeGARotorFromAngles()
{
	
	double N1, N2,N3;

	double thetaRad,phiRad,psiRad;

	//Compute axis of rotation
	//Check limits
	if (m_theta>=180) m_theta=m_theta-180;
	if (m_theta<0) m_theta=m_theta+180;
	if (m_phi>=360) m_phi=m_phi-360;
	if (m_phi<0) m_phi=m_phi+360;
	if (m_psi>=360) m_psi=m_psi-360;
	if (m_psi<0) m_psi=m_psi+360;

	//Convert to radians
	thetaRad=m_theta*M_2PI/360;
	phiRad=m_phi*M_2PI/360;
	psiRad=m_psi*M_2PI/360;

	N3=cos(thetaRad);


	//Use function LPGAConvertRotationVectorToRotor
	N1=sin(thetaRad)*cos(phiRad); //Try this
	N2=sin(thetaRad)*sin(phiRad);
	
	double R[4];
	LPGAConvertRotationVectorToRotor(N1,N2,N3,psiRad,
		&R[0],&R[1],&R[2],&R[3]);
	
	//Note that if psi=0 it gives a null rotation.

	m_GARotR0=R[0];
	m_GARotR12=R[1];
	m_GARotR13=R[2];
	m_GARotR23=R[3];

}


STDMETHODIMP CLPMolecViewer::get_PerspectiveAngle(double *pVal)
{
	*pVal=m_perspective;
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::put_PerspectiveAngle(double newVal)
{
	m_perspective=newVal;
	return S_OK;
}


STDMETHODIMP CLPMolecViewer::GetBitmap(HBITMAP *hbmp)
{
	//Gets bitmap of the current rendering if it exists
	
	*hbmp=NULL; //Default

	if(m_hdc != NULL){
		HBITMAP hbmp0=NULL;

		//Can try either using BitBlt or using GL ReadPixels

		/*
		//Using BitBlt
		//DOESN'T WORK
		//Create hdc for the bitmap
		HDC hdc0=CreateCompatibleDC(m_hdc);
		
		BYTE* bmpdata = new BYTE[m_width*m_height*4];
		hbmp0=CreateBitmap(m_width,m_height,1,32, bmpdata);

		SelectObject(hdc0,hbmp0);
		//Copies opengl drawing to the bitmap
		if (BitBlt(hdc0,0,0,m_width,m_height,m_hdc,0,0,SRCCOPY)){
			
			*hbmp=hbmp0;
			
		}

		//DeleteObject(hbmp0);
		DeleteDC(hdc0);
		*/

		//Using glReadPixels

		BYTE* bmpdata = new BYTE[m_width*m_height*4];

		glReadPixels(0,0,m_width,m_height,GL_BGRA_EXT,GL_UNSIGNED_BYTE,bmpdata);

		//bmpdata will have alpha values=0.
		//To make background transparent, need to find which values
		//of Z-buffer are at far, and set the corresponding alpha values
		//to 0
		long npix=m_width*m_height;

		//Gets zbuffer to apply alpha
		GLfloat* zBuff = new GLfloat[npix];
		glReadPixels(0,0,m_width,m_height, GL_DEPTH_COMPONENT, GL_FLOAT, zBuff);

		//Sets alpha channel depending on zbuffer values
		for (long i=0;i<npix; i++){
			if (zBuff[i]==1.0f){
				bmpdata[(i*4)+3]=0;	
			}else{
				bmpdata[(i*4)+3]=255;
			}
		}
		
		/*
		if (bmpdata!=NULL){
			hbmp0=CreateBitmap(m_width,m_height,1,32, bmpdata);
			*hbmp = hbmp0;
		}
		*/

		//Final image appears mirrored vertically, need to mirror
		
		BYTE* bmpdatamirr = new BYTE[m_width*m_height*4];

		for(long ih=0; ih<m_height; ih++){
			memcpy(bmpdatamirr + ih*m_width*4 , bmpdata + (m_height-ih-1)*m_width*4, m_width*4);
		}


		if (bmpdatamirr!=NULL){
			hbmp0=CreateBitmap(m_width,m_height,1,32, bmpdatamirr);
			*hbmp = hbmp0;
		}
		delete[] bmpdatamirr;


		delete[] bmpdata;

	}	
	
	return S_OK;
}

STDMETHODIMP CLPMolecViewer::SaveMolToPNG(BSTR filename)
{
	//Check number of alpha bits
	//char stemp[16];
	//int bits;
	//glGetIntegerv(GL_ALPHA_BITS,&bits);
	//sprintf(stemp,"%d",bits);
	//MessageBox(stemp,_T("GL_ALPHA_BITS"),MB_OK);

	
	if (filename != NULL){
		//Using unicode
		USES_CONVERSION;
		//m_FileLocation=_T(OLE2T(newVal));
		TCHAR *filename0=new TCHAR[MAX_PATH];
		::wcstombs(filename0,filename,MAX_PATH);

		//Assume I have the pointer of widechar I can use it as LPCWSTR

		//Get bitmap of current screen
		HBITMAP bmp;
		
		this->GetBitmap(&bmp);
		
		
		//TODO: Debug bitmap creation
		//Save bitmap
		if (bmp != NULL){
			//NOT LINKING PROPERLY
			//LPSaveBitmapGDIPlusLib_SavePNG( bmp , (LPCWSTR)&filename0);

			//Use gdiplus available at Platform SDK
			//LPSaveBitmapGDIPlusLib_SavePNG (bmp,(LPCWSTR)filename0);
			LPSaveBitmapGDIPlusLib_SavePNG (bmp,(LPCWSTR)filename);
			
			DeleteObject(bmp);
		}
	}

	return S_OK;
}



STDMETHODIMP CLPMolecViewer::TestShowMessageBox()
{
	//ATL CComControl MessageBox
	//int MessageBox(LPCTSTR lpszText,LPCTSTR lpszCaption = _T(""),UINT nType = MB_OK);

	MessageBox(NULL,_T("Hello World!"),MB_OK);

	return S_OK;
}
